package de.game.actor.tower;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;

import de.core.gameplay.actor.AbstractActor;
import de.core.geometrics.Vector2D;
import de.game.actor.creep.AbstractCreep;
import de.game.actor.tower.projectile.AbstractProjectile;

public abstract class AbstractTower extends AbstractActor implements Tower {

    private final int maxLevel;

    private int reloadTime;
    private int damagePerShot;
    private int attackRange;
    private int currentLevel;

    private long lastTimeTurretShot;
    private AbstractCreep currentTarget;

    private BufferedImage backgroundImage;


    protected AbstractTower(BufferedImage aTurretImage, BufferedImage aBackgroundImage, int aAttackRange,
                            int aReloadTime, int aDamagePerShot, int aX, int aY) {
        this(aTurretImage, aBackgroundImage, aAttackRange, aReloadTime, aDamagePerShot, 1, aX, aY);
    }

    protected AbstractTower(BufferedImage aTurretImage, BufferedImage aBackgroundImage, int aAttackRange,
                            int aReloadTime, int aDamagePerShot, int aMaxLevel, int aX, int aY) {
        super(aTurretImage, aX, aY);
        backgroundImage = aBackgroundImage;
        attackRange = aAttackRange;
        maxLevel = aMaxLevel;
        currentLevel = 1;
        reloadTime = aReloadTime;
        damagePerShot = aDamagePerShot;
    }

    @Override
    public void render(Graphics2D aRenderGraphics, float aInterpolation) {
        AffineTransform originalTransform = aRenderGraphics.getTransform();

        /* DEBUG */
        Point2D renderLocation = getImageRenderLocation(aInterpolation);

        Ellipse2D ellipse = new Ellipse2D.Double(0.0f, 0.0f, getAttackRange() * 2, getAttackRange() * 2);

        AffineTransform rangeTransformation = new AffineTransform();
        rangeTransformation.setToTranslation(renderLocation.getX() - getAttackRange(),
                                             renderLocation.getY() - getAttackRange());

        aRenderGraphics.transform(rangeTransformation);
        aRenderGraphics.setColor(Color.LIGHT_GRAY);
        aRenderGraphics.fill(ellipse);

        aRenderGraphics.setTransform(originalTransform);

        if (backgroundImage != null) {
            Point2D backgroundImageLocation = getBackgroundImageRenderLocation();

            AffineTransform transformation = new AffineTransform();
            transformation.setToTranslation(backgroundImageLocation.getX(),
                                            backgroundImageLocation.getY());

            aRenderGraphics.drawImage(backgroundImage, transformation, null);
        }

        /* DEBUG */
        if (getCurrentTarget() != null) {
            aRenderGraphics.setColor(new Color(255, 0, 0));
            aRenderGraphics.drawLine((int) getLocation().getX(), (int) getLocation().getY(),
                (int) getCurrentTarget().getLocation().getX(), (int) getCurrentTarget().getLocation().getY());
        }

        super.render(aRenderGraphics, aInterpolation);
    }

    protected Point2D getBackgroundImageRenderLocation() {
        double centerX = getLocation().getX() - backgroundImage.getWidth(null) / 2;
        double centerY = getLocation().getY() - backgroundImage.getHeight(null) / 2;
        return new Point2D.Double(centerX, centerY);
    }

    @Override
    public boolean isUpgradePossible() {
        return currentLevel < maxLevel;
    }

    @Override
    public int getCurrentLevel() {
        return currentLevel;
   }

    @Override
    public double getDamagePerSecond() {
        return 1000 / reloadTime * damagePerShot;
    }

    @Override
    public int getDamagePerShot() {
        return damagePerShot;
    }

    @Override
    public int getShotsPerMinute() {
        return 60000 / reloadTime;
    }

    @Override
    public int getAttackRange() {
        return attackRange;
    }

    protected void setReloadTime(int aReloadTime) {
        reloadTime = aReloadTime;
    }

    protected void setDamagePerShot(int aDamagePerShot) {
        damagePerShot = aDamagePerShot;
    }

    protected void setAttackRange(int aAttackRange) {
        attackRange = aAttackRange;
    }

    protected void setCurrentLevel(int aCurrentLevel) {
        currentLevel = aCurrentLevel;
    }

    protected void setBackgroundImage(BufferedImage aBackgroundImage) {
        backgroundImage = aBackgroundImage;
    }

    public AbstractCreep getCurrentTarget() {
        return currentTarget;
    }

    @Override
    public void act() {
        if (getCurrentTarget() == null || !isCreepInRange(getCurrentTarget())) {
            updateCurrentTarget();
        }
        if (!isCreepInRange(getCurrentTarget())) {
            currentTarget = null;
        }
        if (getCurrentTarget() != null) {
            turnToCurrentTarget();
            fireProjectileOnTargetIfReady();
        }
    }

    protected boolean isCreepInRange(AbstractCreep aCreep) {
        for (AbstractCreep creep : getCreepsInRange(getAttackRange())) {
            if (aCreep == creep) {
                return true;
            }
        }
        return false;
    }

    protected void updateCurrentTarget() {
        double highestHealthPoints = .0d;
        List<AbstractCreep> creepsInRange = getCreepsInRange(getAttackRange());
        for (AbstractCreep creep : creepsInRange) {
            if (creep.getCurrentHealtPoints() > highestHealthPoints) {
                currentTarget = creep;
            }
        }
    }

    protected List<AbstractCreep> getCreepsInRange(double aRange) {
        List<AbstractActor> actorsInRange = getActorsInRange(aRange);
        List<AbstractCreep> creepsInRange = new ArrayList<AbstractCreep>();
        for (AbstractActor actor : actorsInRange) {
            if (actor instanceof AbstractCreep) {
                creepsInRange.add((AbstractCreep) actor);
            }
        }
        return creepsInRange;
    }

    protected void turnToCurrentTarget() {
        if (getCurrentTarget() == null) {
            throw new IllegalStateException("Current target must not be null!");
        }
        double rotationInRadians = Math.toRadians(getRotation());
        double turretX = getLocation().getX() + Math.cos(rotationInRadians);
        double turretY = getLocation().getY() + Math.sin(rotationInRadians);

        Vector2D turretVector = new Vector2D(getLocation().getX() - turretX, getLocation().getY() - turretY);
        Vector2D creepVector = new Vector2D(getLocation().getX() - getCurrentTarget().getLocation().getX(),
                                            getLocation().getY() - getCurrentTarget().getLocation().getY());

        double Vangle = Math.toRadians(getRotation() + turretVector.getAngle(creepVector));
        double vX = turretX + Math.cos(Vangle);
        double vY = turretY + Math.sin(Vangle);

        Vector2D vV1 = new Vector2D(turretX - vX, turretY - vY);
        Vector2D vV2 = new Vector2D(turretX - getCurrentTarget().getLocation().getX(),
                                    turretY - getCurrentTarget().getLocation().getY());

        double angleToRotate = turretVector.getAngle(creepVector);
        angleToRotate *= vV1.getAngle(vV2) > 1 ? -1 : 1;
        rotate((int) Math.round(angleToRotate));
    }

    private void fireProjectileOnTargetIfReady() {
        if (System.currentTimeMillis() > lastTimeTurretShot + reloadTime) {
            AbstractProjectile projectile = createNewProjectile();
            getLevel().addActor(projectile);
            lastTimeTurretShot = System.currentTimeMillis();
        }
    }

    protected abstract AbstractProjectile createNewProjectile();

}
